@file: Suppress("unused")

package com.nycreativity.elecreativity.util

import com.nycreativity.elecreativity.entity.po.OrderItem
import org.apache.commons.lang3.StringUtils
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.memberProperties

/**
 * @description 批量插入(基本构建完成...)
 * @author sxt
 * @date 2021/3/14 4:13 下午
 * @version 1.0
 */

object BatchSQL {

    /**
     * 生成批量插入语句
     * @param values List<T> 需要插入的值
     * @param table String? 表名, 默认类名(下划线形式)
     * @return String 生成的批量插入语句
     */
    inline fun <reified T : Any> insert(values: List<T>, table: String? = null): String {
        val tableName = getTableName<T>(table)
        val columns = getExactColumns<T>()
        val valueArray = getValueArray(values, columns)
        return insert {
            tableName {
                columns(*columns.map { UnderlineWithHump.HumpToUnderline(it.name) }.toTypedArray())
                values {
                    valueArray.forEach { value(*it.toTypedArray()) }
                }
            }
        }.render()
    }

    inline fun <reified T : Any> update(values: List<T>, table: String? = null): String {
        val tableName = getTableName<T>(table)
//        T::class.memberProperties.filter { it.hasAnnotation<>() }
        return update {
            tableName set {

            }
        }.render()
    }

    inline fun <reified T : Any> getValueArray(
        values: List<T>,
        columns: ArrayList<KProperty1<T, *>>
    ): ArrayList<List<Any>> {
        val valueArray = ArrayList<List<Any>>()
        values.forEach { column ->
            val tempValueArray = ArrayList<Any>()
            columns.forEach {
                tempValueArray += it.get(column) ?: ""
            }
            valueArray += tempValueArray
        }
        return valueArray
    }

    inline fun <reified T : Any> getTableName(table: String?): String {
        var tableName: String?
        if (StringUtils.isEmpty(table)) {
            tableName = T::class.findAnnotation<Table>()?.tableName
            if (StringUtils.isEmpty(tableName)) {
                tableName = UnderlineWithHump.HumpToUnderline(T::class.simpleName)
            }
        } else {
            tableName = table
        }
        return tableName!!
    }

    inline fun <reified T : Any> getExactColumns(): ArrayList<KProperty1<T, *>> {
        val columns = ArrayList<KProperty1<T, *>>()
        T::class.memberProperties.forEach {
            if (it.hasAnnotation<Column>()) {
                columns += it
            }
        }
        return columns
    }

    /**
     * 用于标记需要插入数据的列
     */
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.PROPERTY)
    annotation class Column

    /**
     * 用于标记po对应的表名
     * @property tableName String 表名
     * @constructor
     */
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.CLASS)
    annotation class Table(val tableName: String)

    /**
     * 用于标记po对应的条件字段
     * @property connector String 连接符 默认: AND
     * @constructor
     */
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.CLASS)
    annotation class Where(val connector: String = "AND")
}

interface Sql {
    /**
     * 渲染Sql
     * @return String 生成的Sql
     */
    fun render(): String
}

class BatchInsertSQL(val name: String) : Sql {

    companion object {
        const val OPERATION = "operation"
        private const val TABLE = "table"
        private const val COLUMNS = "columns"
        private const val VALUES = "values"
    }

    /**
     * sql构成节点
     */
    val sqlNodes: LinkedHashMap<String, String> = LinkedHashMap()

    operator fun String.invoke(block: () -> Unit) {
        this@BatchInsertSQL.sqlNodes[TABLE] = this
        block()
    }

    fun columns(vararg columns: String): Array<String> {
        val columnsArray = arrayOf(*columns)
        this.sqlNodes[COLUMNS] = "(${columnsArray.joinToString(",")})"
        return columnsArray
    }

    fun values(block: () -> Unit) {
        this.sqlNodes[VALUES] = ""
        block()
        val values = this.sqlNodes[VALUES]
        if (!StringUtils.isEmpty(values)) {
            this.sqlNodes[VALUES] = values!!.substring(0, values.length - 1)
        }
    }

    fun values(vararg blocks: () -> String) {
        this.sqlNodes[VALUES] = ""
        blocks.forEach { it() }
        val values = this.sqlNodes[VALUES]
        if (!StringUtils.isEmpty(values)) {
            this.sqlNodes[VALUES] = values!!.substring(0, values.length - 1)
        }
    }

    fun value(vararg array: Any): String {
        val values = this.sqlNodes[VALUES]
        val value = "(${parseValues(array)}),"
        if (values != null) {
            this.sqlNodes[VALUES] += value
            return value
        }
        this.sqlNodes[VALUES] = value
        return value
    }

    private fun parseValues(values: Array<out Any>): String {
        var result = ""
        for (value in values) {
            result += parseValue(value)
            result += ","
        }
        return result.substring(0, result.length - 1)
    }

    private fun parseValue(value: Any): String {
        return if (value is String) {
            "'$value'"
        } else {
            value.toString()
        }
    }

    override fun render(): String {
        return """
            ${sqlNodes[OPERATION]}
            ${sqlNodes[TABLE]}${sqlNodes[COLUMNS]}
            
            VALUES
            
            ${sqlNodes[VALUES]}
        """.trimIndent()
    }
}

class BatchUpdate(val name: String) : Sql {

    companion object {
        const val OPERATION = "operation"
        const val TABLE = "table"
        private const val SET = "set"
        private const val WHERE = "where"
    }

    /**
     * sql构成节点
     */
    val sqlNodes: LinkedHashMap<String, String> = LinkedHashMap()

    /**
     * sql set 构成节点
     */
    private val setNodes: LinkedHashMap<String, String> = LinkedHashMap()

    var tmpCase = ""

    override fun render(): String {
        return """
                UPDATE ${sqlNodes[TABLE]} SET
                ${setNodes.values.toList().joinToString("END, \n").plus("END\n")}
                WHERE
                ${sqlNodes[WHERE]}
        """.trimIndent()
    }

    infix fun String.set(block: () -> Unit) {
        sqlNodes[TABLE] = this
        block()
    }

    operator fun String.invoke(block: () -> Unit) {
        block()
    }


    fun case(what: Any, value: Any) {
        setNodes[tmpCase] += """
            WHEN $what THEN $value 
            
        """.trimIndent()
    }

    infix fun String.case(what: Any): String {
        val case = """
            $this = CASE $what
            
            """.trimIndent()
        setNodes[case] = case
        tmpCase = case
        return case
    }

    fun BatchUpdate.where(block: () -> Unit) {
        sqlNodes[WHERE] = "1 = 1 "
        block()
    }

    fun conditions(block: () -> Unit) {
        block()
    }

    fun condition(condition: String) {
        val trimCondition = condition.trim()
        if (!trimCondition.startsWith("AND") && !trimCondition.startsWith("OR")) {
            sqlNodes[WHERE] += " AND "
        } else {
            sqlNodes[WHERE] += " "
        }
        sqlNodes[WHERE] += trimCondition
    }


}

fun insert(block: BatchInsertSQL.() -> Unit): BatchInsertSQL {
    val insert = BatchInsertSQL("INSERT")
    insert.sqlNodes[BatchInsertSQL.OPERATION] = "INSERT INTO"
    insert.block()
    return insert
}

fun update(block: BatchUpdate.() -> Unit): BatchUpdate {
    val update = BatchUpdate("UPDATE")
    update.block()
    return update
}

fun main() {

//    val update = update {
//        "order" set {
//            ("column1" case "id") {
//                case(1, 111)
//                case(2, 222)
//                case(3, 333)
//            }
//
//            ("column2" case "id") {
//                case(1, "qqq")
//                case(2, "ddd")
//                case(3, "saa")
//            }
//        }
//        where {
//            conditions {
//                condition("id = 1")
//                condition("cd = 1")
//            }
//        }
//    }.render()
//
//    println(update)

//    println(BatchSQL.insert(arrayListOf(OrderItem(1, 10), OrderItem(3, 11), OrderItem(1, 12))))

}












